package kd.bos.openapi.sdk.auth.signauth;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import kd.bos.crypto.EncryptorFactory;
import kd.bos.encrypt.impl.RSAEncrypterUtil;
import kd.bos.openapi.common.result.CustomApiResult;
import kd.bos.openapi.sdk.auth.digestauth.OpenApiDigestAuthUtil;
import kd.bos.openapi.sdk.util.DateUtil;
import kd.bos.openapi.sdk.util.HttpClientUtil;
import kd.bos.openapi.sdk.util.PropertyUtil;
import kd.bos.openapi.sdk.util.SignUtils;
import kd.bos.openapi.security.model.EncryptInfo;
import kd.bos.openapi.security.model.EncryptionEnum;
import kd.bos.openapi.security.model.RequestSecurityDto;
import kd.bos.openapi.security.model.ResponseSecurityDto;
import kd.bos.openapi.security.model.SignInfoDto;
import kd.bos.openapi.security.model.TestInfoRequest;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;

/**
 * @author johnz
 * @Deacription 签名认证工具类
 * @date 2023年10月20日
 */
public class OpenApiSignAuthUtil {
    private final static Logger log = Logger.getLogger(OpenApiSignAuthUtil.class.getName());
    // 默认随机key长度
    private static int DEFAULT_RANDOM_KEY_LENGTH = 16;
    // 默认加密位
    private static int DEFAULT_ENCRYPT_LENGTH = 128;
    // 支持加密算法
    private static Map<String, String> algorithmMap = new HashMap();

    static {
        algorithmMap.put("AES", "AES/GCM/NoPadding");
        algorithmMap.put("SM4", "SM4/GCM/NoPadding");
    }

    public OpenApiSignAuthUtil() {
    }

    /**
     * 产生随机会话key，将内容content加密 并通过RSA公钥通过加密算法algorithm加密会话key
     *
     * @param content
     * @param algorithm
     * @param publicKeyBase64
     * @return
     * @throws Exception
     */
    public static EncryptInfo encrypt(String content, String algorithm, String publicKeyBase64) throws Exception {
        String randomKey = RandomStringUtils.randomAlphabetic(DEFAULT_RANDOM_KEY_LENGTH);
        String result = encryptOrDecrypt(true, content, algorithm, randomKey, DEFAULT_ENCRYPT_LENGTH);
        String dgtlEnvlp = "";
        if (StringUtils.isNotEmpty(publicKeyBase64)) {
            dgtlEnvlp = signByPublicKey(randomKey, publicKeyBase64);
        }
        return EncryptInfo.valueOf(result, dgtlEnvlp);
    }

    /**
     * 根据会话key randomKey和加密算法algorithm 解密已加密的内容content
     *
     * @param content
     * @param algorithm
     * @param randomKey
     * @return
     * @throws Exception
     */
    public static String decrypt(String content, String algorithm, String randomKey) throws Exception {
        return encryptOrDecrypt(false, content, algorithm, randomKey, DEFAULT_ENCRYPT_LENGTH);
    }

    /**
     * 根据加密算法和加密长度 加密或解密内容
     *
     * @param isEncrypt
     * @param content
     * @param algorithm
     * @param randomKey
     * @param encryptLength
     * @return
     * @throws Exception
     */
    public static String encryptOrDecrypt(boolean isEncrypt, String content, String algorithm, String randomKey, int encryptLength) throws Exception {
        if (StringUtils.isEmpty(content)) {
            return "";
        } else if (!algorithmMap.containsKey(algorithm)) {
            throw new Exception("not support algorithm:" + algorithm);
        } else if (StringUtils.isEmpty(randomKey)) {
            throw new Exception("not support randomKey, the randomKey is null");
        } else if (encryptLength < DEFAULT_ENCRYPT_LENGTH) {
            throw new Exception("not support encryptLength, the length should not be less 128, encryptLength:" + encryptLength);
        } else {
            return isEncrypt
                    ? EncryptorFactory.getEncryptor((String) algorithmMap.get(algorithm)).encrypt(content, randomKey, encryptLength)
                    : EncryptorFactory.getEncryptor((String) algorithmMap.get(algorithm)).decrypt(content, randomKey, encryptLength);
        }
    }

    /**
     * 根据公钥加签内容
     *
     * @param content
     * @param publicKeyBase64
     * @return
     * @throws Exception
     */
    public static String signByPublicKey(String content, String publicKeyBase64) throws Exception {
        String result = "";

        try {
            PublicKey publicKey = RSAEncrypterUtil.getPublicKey(publicKeyBase64);
            result = RSAEncrypterUtil.encrypt(content, publicKey);
            return result;
        } catch (Exception var4) {
            throw new Exception("signByPublicKey error:" + var4.getMessage(), var4);
        }
    }

    private static String getPropertyValue(String key) {
        return StringUtils.isEmpty(key) ? "" : PropertyUtil.getProperty(key);
    }

    /**
     * 根据公钥获取签名内容
     *
     * @param content
     * @param publicKeyBase64
     * @return
     * @throws Exception
     */
    public static String unSignByPublicKey(String content, String publicKeyBase64) throws Exception {
        String result = "";
        try {
            PublicKey publicKey = RSAEncrypterUtil.getPublicKey(publicKeyBase64);
            result = RSAEncrypterUtil.decrypt(content, publicKey);
            return result;
        } catch (Exception e) {
            throw new Exception("unSignByPublicKey error:" + e.getMessage(), e);
        }
    }

    /**
     * 根据时间戳，随机字符串和密钥，通过SHA256算法签名
     *
     * @param appSecret SHA256签名密钥
     * @param content   SH256被签名内容
     * @param uuid      随机字符串
     * @param timestamp 时间戳
     * @return 签名内容
     */
    public static String signing(String appSecret, String content, String uuid, String timestamp) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append(content);
        sb.append(timestamp);
        sb.append(uuid);
        return SignUtils.HMACSHA256StrByKey(sb.toString(), appSecret);
    }

    /**
     * 签名认证公钥
     *
     * @param jsonContent
     * @param url
     * @param clazz
     * @param authParam
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> CustomApiResult<T> sendSignApiRequest(String jsonContent, String url, Class<T> clazz, SignAuthParamDto authParam) throws Exception {
        String appSecret = authParam.getAppSecret();
        String bosPublickBase64Key = authParam.getBosPublickBase64Key();
        String openApiSign = authParam.getOpenApiSign();
        EncryptInfo encodeInfo = encrypt(jsonContent, EncryptionEnum.getEncryption(authParam.getStrategy()), bosPublickBase64Key);
        RequestSecurityDto<TestInfoRequest> requestSec = new RequestSecurityDto();
        requestSec.setEncryptData(encodeInfo.getEncryptInfo());
        requestSec.setDgtlEnvlp(encodeInfo.getDgtlEnvlp());
        String uuid = UUID.randomUUID().toString();
        String timestamp = DateUtil.format(new Date(), SignUtils.SIGNATUR_TIME_FORMAT);
        requestSec.setSignatureNonce(uuid);
        requestSec.setTimestamp(timestamp);
        String signString = JSON.toJSONString(requestSec);
        SignInfoDto signInfoDto = new SignInfoDto();
        signInfoDto.setContent(signString);
        signInfoDto.setSignatureNonce(uuid);
        signInfoDto.setDateTime(timestamp);
        String sign = signing(appSecret, signString, uuid, timestamp);
        requestSec.setSignature(sign);
        String requestString = JSON.toJSONString(requestSec);
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("openApiAuth", "5");
        headerMap.put("Content-Type", "application/json");
        headerMap.put("openApiSign", openApiSign);

        if (authParam.isPrint()) {
            log.info("------------------------sendSignApiRequest request.url--------------------------------------------");
            log.info(url);

            log.info("------------------------sendSignApiRequest request.header--------------------------------------------");
            log.info(JSON.toJSONString(headerMap, true));

            log.info("------------------------sendSignApiRequest request.body--------------------------------------------");
            log.info(requestString);
        }

        String responseString = "";
        try {
            responseString = HttpClientUtil.post(url, headerMap, requestString);
        } catch (Exception e) {
            String message = "request.header:" + JSON.toJSONString(headerMap)
                    + " request.body:" + requestString + " response-=" + responseString + " doSignApiRequest send post error:" + e.getMessage();
            log.info(message);
            throw new Exception(message + e.getMessage(), e);
        }
        if (responseString.contains("login.loginBizException")) {
            JSONObject errorResponse = JSON.parseObject(responseString);
            String message = "doSignApiRequest error:" + errorResponse.getString("errorMsg");
            log.info(message);
            log.info("res:"+responseString);
            throw new Exception(responseString);
        }
        ResponseSecurityDto<CustomApiResult> result = null;
        try {
            result = JSON.parseObject(responseString, new TypeReference<ResponseSecurityDto<CustomApiResult>>() {
            }, new com.alibaba.fastjson.parser.Feature[0]);
            timestamp = result.getTimestamp();
            uuid = result.getSignatureNonce();
            signString = result.getSignature();
            result.setSignature(null);
            String signContent = JSON.toJSONString(result);
            String signStr = signing(appSecret, signContent, uuid, timestamp);
            boolean signCheckResult = signString.equalsIgnoreCase(signStr);
            if (!signCheckResult) {
                String message = "结果签名不过: the response is :" + responseString;
                throw new Exception(message);
            }
            String dgtlEnvlp = result.getDgtlEnvlp();
            String encryptData = result.getEncryptData();
            if (encryptData != null && dgtlEnvlp != null) {
                String dgtlEnvlpStr = unSignByPublicKey(dgtlEnvlp, bosPublickBase64Key);
                String decryptData = decrypt(encryptData, EncryptionEnum.getEncryption(Long.valueOf(1L)), dgtlEnvlpStr);
                if (result.getData() != null) {
                    CustomApiResult customApiResult = (CustomApiResult) result.getData();
                    if (clazz.getSimpleName().equalsIgnoreCase("String")) {
                        customApiResult.setData(decryptData);
                    } else {
                        T data = (T) JSON.toJavaObject((JSON) JSON.parseObject(decryptData), clazz);
                        customApiResult.setData(data);
                    }
                } else if (clazz.getSimpleName().equalsIgnoreCase("String")) {
                    result.setData(CustomApiResult.success(decryptData));
                } else {
                    T data = (T) JSON.toJavaObject((JSON) JSON.parseObject(decryptData), clazz);
                    result.setData(CustomApiResult.success(data));
                }
                result.setEncryptData(null);
                result.setDgtlEnvlp(null);
            }
        } catch (Exception e) {
            log.info("res:"+responseString);
            throw new Exception("receive post error:" + e.getMessage(), e);
        }
        return (CustomApiResult<T>) result.getData();
    }

}
